"""
Copyright 2012 Wieser Dietmar, Margit Mutschlechner.

This file is part of Multimedia Social Networking Platform.

Multimedia Social Networking Platform is free software: 
you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Multimedia Social Networking Platform is distributed
in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Multimedia Social Networking Platform.  
If not, see <http://www.gnu.org/licenses/>.

"""

from msnp.service.utils import Hash
import math
from msnp.logger import Logger
from msnp.service.network.model.CustodyModel import CustodyQueueType
from msnp.service.network.logic import ResponsibilityLogic
from msnp.service.network import CustodyQueue, logic
from msnp.service import network
import time

class PeerInfo:
    """Stores all needed information of a single peer.
    
    Attributes:
        __ip: the ip of the current peer (string)
        __ports: a dictionary containing the ports used
        __password: the password (string)
        __name: the user name (string)
        __hashedName: the hashed user name
    """
    
    def __init__(self, name=None, pwd=None, ip=None, ports=None):
        self.__ip = ip
        self.__ports = ports
        if pwd != None:
            pwd = unicode(pwd)
        if name != None:
            name = unicode(name)
        self.__password = pwd
        self.__name = name
        if name != None:
            self.__hashedName = Hash.hash(name)
        
    def getIp(self):
        return self.__ip
    
    def getPorts(self):
        return self.__ports
    
    def getPortForTag(self, tag):
        """This method returns the port assigned to the key 'tag'.
        
        Args:
            tag: the key of the dictinary
        
        Returns:
            the assigned port, or None if not found.
        """
        return self.__ports.get(tag, None)
    
    def getPassword(self):
        return self.__password
    
    def getName(self):
        return self.__name
    
    def getHashedNameString(self):
        """This method returns a string representation of the hashed user name.
        
        Returns:
            the string representation of the hashed user name, or the empty string if the hashed user name is None.
        """
        hashedNameLong = self.getHashedNameLong()
        if hashedNameLong != None:
            return str(hashedNameLong)
        return ""
    
    def getHashedNameLong(self):
        """This method returns a long representation (hex) of the hashed user name.
        
        Returns:
            the long representation (hex) of the hashed user name, or the empty string if the hashed user name is None.
        """
        if self.__hashedName != None:
            return long(self.__hashedName, 16)
        return None
    
    def getNameBundle(self):
        """This method returns the string representation of the hashed user name and the user name itself.
        
        Returns:
            the string representation of the hashed user name and the user name itself.
        """
        if self.getName() != None and self.getHashedNameString() != None:
            return self.getHashedNameString() + " (" + self.getName() + ")"
        return ""
    
    def toString(self):
        """Returns a string representation of the current PeerInfo instance."""
        
        returnString = "Identity:\nip: " + (self.__ip if self.__ip != None else "Not defined") + "\nname: " + self.getNameBundle() + "\nportMapping: \n"
        for connectionType, port in self.__ports.items():
            returnString += " - " + connectionType + "(" + str(port) + ")"
        returnString += "\n------------------"
        return returnString
    
    
class Peer:
    """This class collects the information of the current peer itself and all relevant information of the network.
    
    Attributes:
        __authenticated: boolean determining whether the current peer is already authenticated.
        __myIdentity: the current identity (PeerInfo instance)
        __predecessor: the PeerInfo instance of the predecessor
        __finger: a dictionary containing all fingers (PeerInfo instances)
        __isLowLevelPeer: boolean determining whether the current peer is a low level peer or not.
        __responsibleSuperPeer: the responsible super peer (PeerInfo instance - only set if the current peer is a low level peer)
        __lowLevelPeers: a list of online low level peers (PeerInfo instances)
        __isLogin: boolean determining whether the authentication process is a login or a registration.
    """
    
    def __init__(self, myIdentity=None, predecessor=None):
        self.__authenticated = False
        self.__myIdentity = myIdentity
        self.__predecessor = predecessor
        self.__finger = {}
        self.__isLowLevelPeer = True
        self.__responsibleSuperPeer = None
        self.__lowLevelPeers = {}
        self.__isLogin = True
        self.initFingers()
        
        
    #############
    ## ISLOGIN ##
    #############  
    
        
    def setIsLogin(self, isLogin):
        self.__isLogin = isLogin
        
    def getIsLogin(self):
        return self.__isLogin
    
    
    ###################
    ## AUTHENTICATED ##
    ###################    
    
    def isAuthenticated(self):
        return self.__authenticated

    def setAuthenticated(self, isAuth):
        self.__authenticated = isAuth
        
            
    
    #################
    ## MY_IDENTITY ##
    #################
        
    def getMyIdentity(self):
        return self.__myIdentity
    
    
    def setMyIdentity(self, myIdentity):
        self.__myIdentity = myIdentity
        
        
    #################
    ## PREDECESSOR ##
    #################
            
    def getPredecessor(self):
        return self.__predecessor
    
    def setPredecessor(self, predecessor):
        if predecessor == None or predecessor.getName() == self.__myIdentity.getName():
            Logger.info("set predecessor to None")
            self.__predecessor = None
        else:
            Logger.info("set predecessor to " + predecessor.getName())
            self.__predecessor = predecessor
            
        
    #################
    ## FINGERTABLE ##
    #################
    
    def initFingers(self):
        """Initializes the dictionary of fingers with the index i as key and None as value."""
        m = Hash.getHashRange()
        for i in range(0, m):
            self.setFinger(None, i)
    
    def getSuccessor(self):
        """Returns the first entry of the fingertable."""
        
        return self.getFingerById(0)
    
    def setSuccessor(self, successor):
        """Sets the first entry of the fingertable to the given argument 'successor'."""
        
        self.setFinger(successor, 0)
    
    def getFingers(self):
        """Returns the whole dictionary of fingers."""
        
        return self.__finger
    
    def getFingerById(self, i):
        """Returns the i-th entry of the dictionary of fingers."""
        
        if len(self.__finger) > 0:
            return self.__finger.get(i)
    
    def setFinger(self, fingerIdentity, i):
        """This method the i-the finger
        
        Args:
            i: integer
            fingerIdentity: the identity (PeerInfo instance)
        """
        if fingerIdentity != None and fingerIdentity.getName() == self.__myIdentity.getName():
            self.__finger[i] = None
        else:
            self.__finger[i] = fingerIdentity
    
    def fingerToString(self):
        """Returns a string representation of all fingers together with the indices."""
        returnString = ""
        for i, finger in self.__finger.items():
            if finger != None:
                returnString += str(i) + ": " + finger.getNameBundle() + "\n"
            else:
                returnString += str(i) + ": None\n"
        return returnString
    
        
        
    #######################
    ## IS_LOW_LEVEL_PEER ##
    #######################
    
    def isLowLevelPeer(self):
        return self.__isLowLevelPeer
    
    def setIsLowLevelPeer(self, isLowLevelPeer):
        self.__isLowLevelPeer = isLowLevelPeer
        
           
    ###########################
    ## RESPONSIBLE_SUPERPEER ##
    ###########################
    
    def getResponsibleSuperPeer(self):
        return self.__responsibleSuperPeer
    
    def setResponsibleSuperPeer(self, respSuperPeer):
        self.__responsibleSuperPeer = respSuperPeer
    

        
    #####################
    ## LOW_LEVEL_PEERS ##
    #####################
    
    def getLowLevelPeers(self):
        return self.__lowLevelPeers
    
    def getLowLevelPeerByName(self, name):
        """Returns the low level peer that has the key 'hashedNameLong' or None if there is no such peer."""
        return self.__lowLevelPeers.get(name)
        
    def getLowLevelPeersAndMessagesForName(self, joinedPeerName):
        """This method collects all low level peers and messages the new super peer is responsible for.
        
        Args:
            joinedPeerName: the name of the upgrading peer (string)
        
        Returns:
            a list of low level peers the new super peer 'joinedPeerName' is responsible for and all corresponding custody messages."""
        llPeerList = []
        messages = []
        for (llPeerName, llPeer) in self.__lowLevelPeers.items():
            if llPeerName != joinedPeerName:
                llPeerHashNameLong = long(Hash.hash(llPeerName), 16)
                joinedPeerHashNameLong = long(Hash.hash(joinedPeerName), 16)
                if ResponsibilityLogic.isResponsible(self.__myIdentity.getHashedNameLong(), llPeerHashNameLong, joinedPeerHashNameLong):
                    llPeerList.append(llPeer)
                    Logger.info("  low level peer " + llPeerName + " has to be shifted")
                    #only shift authentication and send-messages. no hello -> is not in the lowlevelpeerlist, same for shift_peer_message
                    temp = CustodyQueue.retrieveAndDeleteList(llPeerName, [CustodyQueueType.AUTH_RESULT_MESSAGE], False)
                    for msg in temp:
                        messages.append(msg)
        return (llPeerList, messages)
    
    def addLowLevelPeer(self, name, identity):
        """This method adds a low level peer to the low level peers dictionary
        
        Args:
            name: the name of the low level peer (key - string)
            identity: the low level peer's PeerInfo instance (value)
        """
        if identity != None:
            self.__lowLevelPeers[name] = LowLevelPeerEntry(identity)
            
    def addLowLevelPeers(self, llPeerList):
        """This method adds a list of low level peers to the low level peers dictionary.
        
        Args:
            llPeerList: a list of low level peers (PeerInfo instances)
        """
        for peer in llPeerList:
            peerIdentity = peer.getIdentity()
            peerName = peer.getIdentity().getName()
            self.addLowLevelPeer(peerName, peerIdentity)
            Logger.info("low level peer " + peerName + " added")

    def removeAllLowLevelPeers(self):
        """This method resets the dictinary of low level peers"""
        self.__lowLevelPeers = {}

    def removeLowLevelPeer(self, name):
        """Removes a specific low level peer from the dictionary depending on the key.
        
        Args:
            name: the name of the low level peer that has to be removed.
        """
        if self.__lowLevelPeers.__contains__(name):
            del self.__lowLevelPeers[name]
        
    def removeLowLevelPeers(self, llPeerList):
        """This method removes a list of low level peers.
        
        Args:
            llPeerList: a list of low level peers (PeerInfo instances)
        """
        for peer in llPeerList:
            peerName = peer.getIdentity().getName()
            self.removeLowLevelPeer(peerName)
        
        
    ##########################
    ## CONNECTED_IDENTITIES ##
    ##########################
    
    def getAllConnectedIdentitiesWithoutPredecessor(self):
        """This method collects all connected identities without the predecessor.
        
        Returns:
            a list of PeerInfo instances
        """
        connectedIdentities = self.getAllConnectedIdentities()
        if self.__predecessor != None and connectedIdentities.__contains__(self.__predecessor):
            connectedIdentities.remove(self.__predecessor)
        return connectedIdentities
    
    def getAllConnectedIdentities(self):
        """This method collects all connected identities.
        
        Returns:
            a list of PeerInfo instances
        """
        allConnectedIdentities = []
        for i, finger in self.__finger.items():
            if finger != None and self.containsIdentity(allConnectedIdentities, finger) == False:
                allConnectedIdentities.append(finger)
        if self.getPredecessor() != None and self.containsIdentity(allConnectedIdentities, self.getPredecessor()) == False:
            allConnectedIdentities.append(self.getPredecessor())
        return allConnectedIdentities
    
    def containsIdentity(self, allConnectedIdentities, searchedIdentity):
        """This method determines whether the searchedIdentity is already contained in allConnectedIdentities
        
        Args:
            allConnectedIdentities: a list of PeerInfo instances
            searchedIdentity: a PeerInfo instance
            
        Returns:
            True if searchedIdentity is contained in the list allConnectedIdentities, False otherwise.
        """
        for identity in allConnectedIdentities:
            if identity.getName() == searchedIdentity.getName():
                return True
        return False
    
                    
    ######################
    ## NEAREST_IDENTITY ##
    ######################                
    
    def getNearestIdentity(self, hashedNameLong):
        """Calculates the finger that is smaller or equal than and nearest to the given 'hashedNameLong'.
        
        If that one is None, then the biggest finger that is not None and smaller than the nearest finger is returned.
        
        Args:
            hashedNameLong: a long
            
        Returns:
            the nearest identity (PeerInfo instance) together with the index
        """
        # responsible peer if its a low level
        if self.__isLowLevelPeer:
            return (self.__responsibleSuperPeer, 0)
        # myself if I am responsible
        if logic.ResponsibilityLogic.amIResponsibleIdentityFor(hashedNameLong):
            return (self.__myIdentity, 0)
        # predecessor if its my predecessors name
        if self.__predecessor.getHashedNameLong() == hashedNameLong:
            return (self.__predecessor, -1)
        #first subtract my hashvalue from the 'hashedNameLong' 
        tmp = hashedNameLong - self.__myIdentity.getHashedNameLong()
        #modulo
        tmp = tmp % (2 ** Hash.getHashRange())
        #get the logarithm of basis 2 from the calculated value
        tmp = math.log(tmp, 2)
        tmp = math.floor(tmp)
        #convert double to int
        tmp = int(tmp)
        finger = self.getFingerById(tmp)
        if finger == None:
            (finger, tmp) = self.getSmallerFingerThan(tmp - 1)
        return (finger, tmp)


    def getNearestIdentityForCrash(self, hashedNameLong):
        """Calculates the nearest identity for a crash.
        
        If that one is None, then the biggest finger that is not None and smaller than the nearest finger is returned.
        
        Args:
            hashedNameLong: the hashed name (long) of the crashed identity
            
        Returns:
            the nearest identity
        """
        (finger, i) = self.getNearestIdentity(hashedNameLong)
        #want to find predecessor
        # find finger that is smaller than the crashed one
        # not only compare equality because if we have processed a leave where we now process a crash
        # we have already reset the finger to the successor of the crashed identity
        myHashedName = network.NetworkController.getMySelf().getMyIdentity().getHashedNameLong()
        while ResponsibilityLogic.isResponsible(myHashedName, hashedNameLong, finger.getHashedNameLong()) and finger.getHashedNameLong() == hashedNameLong:
            (finger, i) = self.getSmallerFingerThan(i - 1)
        return finger
    
    
    def getSmallerFingerThan(self, startIndex):
        """This method returns the first smaller finger than 'startIndex' that is not None."""
        #try all fingers from startIndex to 0
        for i in range(startIndex, -1, -1):
            finger = self.getFingerById(i)
            if finger != None:
                return (finger, i)
        # default return predecessor
        return (self.getPredecessor(), -1)
    
    
class LowLevelPeerEntry():
    """Class for low level peers and their last keep-alive-message.
    
    Arguments:
        __identity: the PeerInfo instance of the corresponding low level peer
        __lastKeepAlive: the time stamp of the last keep alive sent by the corresponding low level peer
    """
    
    def __init__(self, identity):
        self.__identity = identity
        self.__lastKeepAlive = time.time()
        
    def getIdentity(self):
        return self.__identity
    
    def getLastKeepAlive(self):
        return self.__lastKeepAlive
    
    def updateLastKeepAlive(self):
        """This method sets the last keep alive to the current time stamp"""
        self.__lastKeepAlive = time.time()
